package p.ithorns.tools.excel.read;

import cn.idev.excel.analysis.v07.XlsxSaxAnalyser;
import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.context.AnalysisContext;
import cn.idev.excel.enums.CellDataTypeEnum;
import cn.idev.excel.event.AnalysisEventListener;
import cn.idev.excel.exception.ExcelAnalysisException;
import cn.idev.excel.exception.ExcelDataConvertException;
import cn.idev.excel.metadata.Head;
import cn.idev.excel.metadata.data.ReadCellData;
import cn.idev.excel.read.metadata.holder.ReadRowHolder;
import cn.idev.excel.read.metadata.holder.ReadSheetHolder;
import cn.idev.excel.read.metadata.property.ExcelReadHeadProperty;
import cn.idev.excel.support.cglib.beans.BeanMap;
import cn.idev.excel.util.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import p.ithorns.tools.excel.AssistTools;
import p.ithorns.tools.excel.EasyExcelUtil;
import p.ithorns.tools.excel.Validators;
import p.ithorns.tools.excel.exception.FieldValidateException;
import p.ithorns.tools.excel.exception.ImportException;
import p.ithorns.tools.excel.exception.RowValidateException;
import p.ithorns.tools.excel.model.FieldInfo;
import p.ithorns.tools.excel.model.RowError;

import javax.validation.ConstraintViolation;
import java.lang.reflect.ParameterizedType;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.atomic.LongAdder;

/**
 * excel导入处理基类
 *
 * @author excel导入处理基类
 * @since 2023/5/31 18:44
 */
@Slf4j
@SuppressWarnings({"unchecked", "rawtypes"})
public abstract class BaseImportHandler2<T, E> extends AnalysisEventListener<T> {

    /**
     * 批次数量
     */
    private static final int BATCH_SIZE = 1000;


    /**
     * 导入类型
     */
    private final Class<T> importClass;


    /**
     * 字段信息
     */
    private final Map<String, FieldInfo> filedInfoMap;

    /**
     * 临时数据存储
     */
    private List<T> importList = ListUtils.newArrayListWithExpectedSize(BATCH_SIZE);

    /**
     * 错误行信息
     */
    private List<RowError<T>> rowErrors;

    /**
     * 处理成功数量
     */
    private final LongAdder successNum;


    public BaseImportHandler2() {
        // 获取泛型
        this.importClass = (Class<T>) ((ParameterizedType) this.getClass()
                .getGenericSuperclass())
                .getActualTypeArguments()[0];

        // 字段表
        this.filedInfoMap = AssistTools.resolveExcelFieldMap(importClass);

        this.successNum = new LongAdder();
    }


    /**
     * Sheet工作表名称
     * <p>
     * 注意：如果这里设置了SheetName，就必须和下载模版方法({@link EasyExcelUtil#doExport})设置的值保持一致！<br/>
     * {@code EasyExcel.sheet(sheetName)}
     * <p>
     * 参考{@link XlsxSaxAnalyser#execute}方法中line#257{@link SheetUtils#match}
     *
     * @return sheet页名称
     */
    public abstract String getSheetName();


    /**
     * Excel对象转Entity对象
     *
     * @param xls -Excel对象
     * @return Entity对象
     */
    public abstract Collection<E> convert(Collection<T> xls);


    /**
     * 入库
     *
     * @param entities -待导入的实体
     * @return 实际导入行数
     */
    public abstract int saveToDb(Collection<E> entities);


    /**
     * 在读取表格前做准备工作
     * <p>
     * 这个操作是全局的，只会操作一次，在表头检验通过后执行，
     */
    public void doBeforeAllAnalysed() {
    }

    /**
     * 头行数
     * <p>
     * 标记数据的开始行
     * 由于包含了约束行，故默认为2
     *
     * @return 表格标题行数
     */
    public int getHeadRow() {
        return 2;
    }

    /**
     * 校验行记录
     *
     * @param row -行记录
     */
    public void validateRow(T row) throws RowValidateException {
    }

    /**
     * 入库前操作
     *
     * @param imports -待导入的批数据(实体)列表
     */
    public void doBeforeSave(List<T> imports) {
    }

    /**
     * 入库前校验
     *
     * @param e -entity
     * @throws RowValidateException -
     */
    public abstract void validateEntity(E e) throws RowValidateException;


    /**
     * 导入后操作
     *
     * @param entities -导入的批数据列表
     */
    public void doAfterSave(Collection<E> entities) {
    }


    /**
     * 每批处理数量
     *
     * @return 数量值, 默认为BATCH_COUNT(1000)
     */
    protected int getBatchSize() {
        return BATCH_SIZE;
    }


    /**
     * 导入类
     *
     * @return Class<T>具体导入类
     */
    public Class<T> getImportClass() {
        return importClass;
    }

    /**
     * 错误行信息
     *
     * @return 错误列表
     */
    public List<RowError<T>> getRowErrors() {
        return rowErrors;
    }


    /**
     * 导入成功数量
     *
     * @return -成功数量
     */
    public int getSuccessNum() {
        return successNum.intValue();
    }


    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        // 重置数据
        this.reset();

        final int rowIndex = context.readRowHolder().getRowIndex();
        filedInfoMap.forEach((key, fi) -> {
            // 跳过非表头行
            int headRow = fi.getHeadRow();
            if (rowIndex != headRow) {
                return;
            }

            int columnIndex = fi.getIndex();
            String headName = headMap.get(columnIndex);
            if (StringUtils.isEmpty(headName)) {
                throw new ExcelAnalysisException("Excel数据解析异常: 请检查Excel表格式是否正确.");
            }

            String fieldAlias = fi.getMainAlias();
            if (!headName.equals(fieldAlias)) {
                throw new ExcelAnalysisException("Excel数据解析异常，请检查Excel表列头是否对应.");
            }
        });

        // 只做一次初始化
        if (rowIndex == 0) {
            // 表头检验通过后执行数据校验前操作
            doBeforeAllAnalysed();
        }
    }


    @Override
    public void invoke(T x, AnalysisContext context) {
        // 校验行数据
        validateImportItem(x, context);

        // 加入到待导入列表
        importList.add(x);

        // 批处理100
        if (importList.size() >= getBatchSize()) {
            // 进行批处理
            doImportBatch();
        }
    }


    /**
     * 校验行记录
     *
     * @param record  -行记录
     * @param context -Excel上下文
     */
    private void validateImportItem(T record, AnalysisContext context) {
        // 表数据
        ReadSheetHolder readSheetHolder = context.readSheetHolder();

        // 行索引
        final Integer rowIndex = readSheetHolder.getRowIndex();

        Set<ConstraintViolation<T>> constraintSet = Validators.validate(record);
        if (CollectionUtils.isNotEmpty(constraintSet)) {
            constraintSet.forEach(cv -> {
                // 字段名
                String filedName = cv.getPropertyPath().toString();
                FieldInfo fieldInfo = filedInfoMap.get(filedName);

                Integer fieldIndex = null;
                String fieldAlias = null;

                if (null != fieldInfo) {
                    // 列索引
                    fieldIndex = fieldInfo.getIndex() + 1;
                    // 列别名
                    fieldAlias = fieldInfo.getMainAlias();
                }

                String msg = MessageFormat.format("{0}->{1}", fieldAlias, cv.getMessage());
                throw new FieldValidateException(rowIndex, fieldIndex, fieldAlias, msg);
            });
        }

        // 用户自定义行校验
        validateRow(record);
    }


    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        log.info("所有数据解析完成!");
        // 进行批处理
        doImportBatch();
    }

    /**
     * 执行导入
     */
    private void doImportBatch() {
        if (CollectionUtils.isEmpty(importList)) {
            return;
        }

        long start = System.currentTimeMillis();
        log.info("开始单次批处理");

        // 自定义入库前操作
        doBeforeSave(importList);

        // 转换实体
        Collection<E> entities = convert(importList);

        // 执行入库前校验
        validateBeforeSave(entities);

        // 入库
        int count = saveToDb(entities);

        // 自定义入库后操作
        doAfterSave(entities);

        // 累加成功数量
        successNum.add(count);

        // 存储完成清理 list
        this.importList = ListUtils.newArrayListWithExpectedSize(getBatchSize());
        log.info("结束单次批处理, 耗时:{}", System.currentTimeMillis() - start);
    }


    /**
     * 入库前校验
     *
     * @param entities -
     */
    private void validateBeforeSave(Collection<E> entities) {
        Iterator<E> iter = entities.iterator();
        while (iter.hasNext()) {
            E xls = iter.next();
            try {
                validateEntity(xls);
            } catch (RowValidateException e) {
                iter.remove();
                throw e;
            }
        }
    }

    @Override
    public void onException(Exception e, AnalysisContext context) {
        // 数据转换异常
        if (e instanceof ExcelDataConvertException) {
            RowError<T> error = handleDataConvertError(context, (ExcelDataConvertException) e);
            rowErrors.add(error);
            return;
        }

        // 字段校验异常
        if (e instanceof FieldValidateException) {
            RowError<T> error = handleFieldValidateError(context, (FieldValidateException) e);
            rowErrors.add(error);
            return;
        }

        // 行数据校验异常
        if (e instanceof RowValidateException) {
            RowError<T> error = handleRowValidateError(context, (RowValidateException) e);
            rowErrors.add(error);
            return;
        }

        String message = e.getMessage();

        // 表
        ReadSheetHolder sheet = context.readSheetHolder();
        Integer sheetNo = sheet.getSheetNo();

        // 如果是某一个单元格的转换异常 能获取到具体行号
        ReadRowHolder row = context.readRowHolder();
        Integer rowIndex = row.getRowIndex();

        log.error("Excel数据解析异常(表{}第{}行): {}.", sheetNo, rowIndex, message);

        // 其他异常直接抛出，终止解析。 例如: 表头不对应
        throw new ImportException(message);
    }

    /**
     * 重置/清理缓存数据
     */
    public void reset() {
        this.importList = ListUtils.newArrayListWithExpectedSize(getBatchSize());
        this.rowErrors = new ArrayList<>(getBatchSize());
        this.successNum.reset();
    }

    private RowError<T> handleDataConvertError(AnalysisContext context, ExcelDataConvertException e) {
        ReadSheetHolder sheet = context.readSheetHolder();
        ReadRowHolder row = context.readRowHolder();
        Integer rowIndex = e.getRowIndex();
        Integer columnIndex = e.getColumnIndex();

        String message = e.getMessage();
        Integer sheetNo = sheet.getSheetNo();
        String sheetName = sheet.getSheetName();

        Object rowData = convertRowToJavaObj((Map) row.getCellMap(), sheet, context);

        RowError.RowErrorBuilder<T> builder = RowError.<T>builder().sheetName(sheetName).sheetNo(sheetNo).rowIndex(rowIndex).msg(message).rowData((T) rowData);


        log.warn("Excel数据转换异常(表{}第{}行第{}列): {}", sheetNo, rowIndex, columnIndex, message);
        String[] names = e.getExcelContentProperty().getField().getAnnotation(ExcelProperty.class).value();
        if (null != names && names.length > 0) {
            builder.columnIndex(columnIndex).columnName(names[0]);
        }

        return builder.build();
    }

    private RowError<T> handleFieldValidateError(AnalysisContext context, FieldValidateException e) {

        ReadSheetHolder sheet = context.readSheetHolder();
        ReadRowHolder row = context.readRowHolder();

        Integer rowIndex = e.getRowIndex();
        Integer columnIndex = e.getColumnIndex();
        String columnName = e.getColumnName();

        String message = e.getMessage();
        Integer sheetNo = sheet.getSheetNo();
        String sheetName = sheet.getSheetName();

        Object rowData = convertRowToJavaObj((Map) row.getCellMap(), sheet, context);
        log.warn("Excel字段校验异常(表{}第{}行第{}列): {}.", sheetNo, rowIndex, columnIndex, message);
        return RowError.<T>builder().sheetName(sheetName).sheetNo(sheetNo).rowIndex(rowIndex).columnIndex(columnIndex).columnName(columnName).msg(message).rowData((T) rowData).build();
    }

    private RowError<T> handleRowValidateError(AnalysisContext context, RowValidateException e) {
        ReadSheetHolder sheet = context.readSheetHolder();
        ReadRowHolder row = context.readRowHolder();

        Integer rowIndex = sheet.getRowIndex();
        String message = e.getMessage();
        Integer sheetNo = sheet.getSheetNo();
        String sheetName = sheet.getSheetName();

        Object rowData = convertRowToJavaObj((Map) row.getCellMap(), sheet, context);
        log.warn("Excel行校验异常(表{}第{}行): {}.", sheetNo, rowIndex, message);
        return RowError.<T>builder().sheetName(sheetName).sheetNo(sheetNo).rowIndex(rowIndex).msg(message).rowData((T) rowData).build();
    }


    /**
     * 转换行数据到Java对象
     *
     * @param cellDataMap     -行数据
     * @param readSheetHolder -表信息
     * @param context         - 上下文
     * @return Object
     */
    private Object convertRowToJavaObj(Map<Integer, ReadCellData<?>> cellDataMap, ReadSheetHolder readSheetHolder, AnalysisContext context) {
        ExcelReadHeadProperty excelReadHeadProperty = readSheetHolder.excelReadHeadProperty();
        Object resultModel;
        try {
            resultModel = excelReadHeadProperty.getHeadClazz().getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            throw new ExcelDataConvertException(context.readRowHolder().getRowIndex(), 0,
                    new ReadCellData<>(CellDataTypeEnum.EMPTY), null,
                    "Can not instance class: " + excelReadHeadProperty.getHeadClazz().getName(), e);
        }
        Map<Integer, Head> headMap = excelReadHeadProperty.getHeadMap();
        BeanMap dataMap = BeanMapUtils.create(resultModel);
        for (Map.Entry<Integer, Head> entry : headMap.entrySet()) {
            Integer index = entry.getKey();
            Head head = entry.getValue();
            String fieldName = head.getFieldName();
            if (!cellDataMap.containsKey(index)) {
                continue;
            }
            ReadCellData<?> cellData = cellDataMap.get(index);
            Class<?> headClazz = readSheetHolder.excelReadHeadProperty().getHeadClazz();
            Object value = ConverterUtils.convertToJavaObject(cellData, head.getField(),
                    ClassUtils.declaredExcelContentProperty(dataMap, headClazz, fieldName, readSheetHolder),
                    readSheetHolder.converterMap(), context, context.readRowHolder().getRowIndex(), index);
            if (value != null) {
                dataMap.put(fieldName, value);
            }
        }
        return resultModel;
    }

}